package org.jeecg.modules.miniAppCustomerEndPoint.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.jx.constant.JxSysConfigConstants;
import org.jeecg.modules.jx.utils.JxCryptUtil;
import org.jeecg.modules.jx.utils.JxHttpTemplate;
import org.jeecg.modules.miniAppCustomerEndPoint.constant.JxCustomerConstant;
import org.jeecg.modules.miniAppCustomerEndPoint.constant.JxRedisConstant;
import org.jeecg.modules.miniAppCustomerEndPoint.entity.JxUserTransactionsDo;
import org.jeecg.modules.miniAppCustomerEndPoint.entity.JxPromotionDetailDo;
import org.jeecg.modules.miniAppCustomerEndPoint.entity.JxUserDo;
import org.jeecg.modules.miniAppCustomerEndPoint.mapper.JxUserTransactionsMapper;
import org.jeecg.modules.miniAppCustomerEndPoint.mapper.JxPromotionDetailMapper;
import org.jeecg.modules.miniAppCustomerEndPoint.mapper.JxUserMapper;
import org.jeecg.modules.miniAppCustomerEndPoint.utils.JxUserUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Api(tags = "购物券")
@RestController
@RequestMapping("/jx/jxPromotion")
@Slf4j
public class JxPromotionController {

    @Autowired
    private JxUserMapper jxUserMapper;

    @Autowired
    private JxUserTransactionsMapper jxUserTransactionsMapper;

    @Autowired
    private JxPromotionDetailMapper jxPromotionDetailMapper;

    @Autowired
    private JxCryptUtil jxCryptUtil;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 接口 3.6 获取我的推广 概览信息
     * @param request
     * @param userIdForDebug
     * @return
     */
    @GetMapping("/queryMyPromotionSummary")
    public Result<?> queryMyPromotionSummary(
            HttpServletRequest request,
            @RequestParam(value = "userId", required = false) String userIdForDebug) {

        String token = request.getHeader("X-Access-Token");
        String userId = JxUserUtil.getUserId(token, userIdForDebug);

        JxUserDo jxUserDo = jxUserMapper.selectById(userId);
        JSONObject resultData = new JSONObject();
        if (jxUserDo == null) {
            resultData.put("nickName", JxCustomerConstant.USER_DEFALUT_NICKNAME);
            resultData.put("promotionSum", 0);
            resultData.put("promotionYesterday", 0);
            resultData.put("promotionWithdraw", 0);
        } else {
            resultData.put("nickName", jxUserDo.getNickName());
            resultData.put("promotionSum", jxUserDo.getRemainingPromotionValue());
            resultData.put("promotionYesterday", jxUserDo.getPromotionYesterday());
            resultData.put("promotionWithdraw", jxUserDo.getPromotionWithdraw());
        }

        return Result.ok(resultData);
    }

    /**
     * 接口3.7 提现佣金到 用户自己的微信钱包
     * @param request
     * @param userIdForDebug
     * @param value 提现金额，单位为分
     * @return
     */
    @GetMapping("/withdrawPromotionFee")
    public Result<?> withdrawPromotionFee(
            HttpServletRequest request,
            @RequestParam(value = "userId", required = false) String userIdForDebug,
            @RequestParam(value = "value") Integer value) {

        String token = request.getHeader("X-Access-Token");
        String userId = JxUserUtil.getUserId(token, userIdForDebug);
        JxUserDo jxUserDo = jxUserMapper.selectById(userId);
        Integer remainingPromotionFee = jxUserDo.getRemainingPromotionValue() - jxUserDo.getPromotionWithdraw();
        if (value > remainingPromotionFee) {
            log.error("用户 {}, 剩余可提现金额 {}, 申请提现金额 {}， 无法提现", userId, remainingPromotionFee, value);
            return Result.error("申请提现的金额，超过用户可提现的推广佣金金额");
        } else {
            try {
                withdrawPromotionFee(jxUserDo.getId(), jxUserDo.getOpenId(), jxUserDo.getRemainingPromotionValue(), jxUserDo.getPromotionWithdraw(), value);
            } catch (Exception e) {
                e.printStackTrace();
                return Result.error("申请提现的金额处理异常，请联系后台技术人员进行处理");
            }
        }

        return Result.ok("提现成功");
    }

    /**
     * 执行如下3个操作
     * 1. 调用微信支付接口，进行转账，从系统的微信商户账号，转账到 用户的微信钱包
     * 2. 向推广佣金交易记录表 jx_promotion_detail 表中添加一条提现记录
     * 3. 将 jx_user 表中的 promotion_withdraw 累加 本次提现的金额
     * @param openId 消费者的 openid
     * @param value 提现金额
     */
    @Transactional(rollbackFor = Exception.class)
    public void withdrawPromotionFee(String userId, String openId,Integer promotionSum, Integer promotionWithdrew, Integer value) throws Exception {

        // todo 调用微信支付接口，进行转账，从系统的微信商户账号，转账到 用户的微信钱包

        // 在 jx_promotion_detail 表中添加一条提现记录
        JxUserTransactionsDo promotionDetailDo = new JxUserTransactionsDo();
        promotionDetailDo.setUserId(userId);
        promotionDetailDo.setType(201);
        promotionDetailDo.setValue(value);
        promotionDetailDo.setOrderId("");
        promotionDetailDo.setCreateTime(new Date());
        int count = jxUserTransactionsMapper.insert(promotionDetailDo);

        if (count != 1) {
            // 主动抛出异常，触发 @Transactional 的 事务回滚 rollback sql命令的发送，回滚事务
            throw new Exception("向 推广佣金交易明细表添加记录失败");
        }

        // 将 jx_user 表中的 promotion_withdraw 累加 本次提现的金额
        // 此处不必再次访问 jx_user表获取 promotion_withdraw，因为其他地方不会对此字段的值进行修改，不存在脏读 的问题
        JxUserDo jxUserDo = new JxUserDo();
        jxUserDo.setId(userId);
        jxUserDo.setPromotionWithdraw(promotionWithdrew + value);
        jxUserDo.setRemainingPromotionValue(promotionSum - value);
        int updatedCount = jxUserMapper.updateById(jxUserDo);
        if (updatedCount != 1) {
            // 主动抛出异常，触发 @Transactional 的 事务回滚 rollback sql命令的发送，回滚事务
            throw new Exception("更新用户推广佣金已提现总金额失败");
        }
    }

    /**
     * 接口 3.8 分页查询过往提现记录
     * @return
     */
    @GetMapping("/queryWithdrawRecords")
    public Result<?> queryWithdrawRecords(
            HttpServletRequest request,
            @RequestParam(value = "userId", required = false) String userIdForDebug,
            @RequestParam("pageNo") Integer pageNo,
            @RequestParam("pageSize") Integer pageSize){

        String token = request.getHeader("X-Access-Token");
        String userId = JxUserUtil.getUserId(token, userIdForDebug);

        Page<JxUserTransactionsDo> page = new Page<>(pageNo, pageSize);
        Page<JxUserTransactionsDo> pageList = jxUserTransactionsMapper.selectPage(page, new LambdaQueryWrapper<JxUserTransactionsDo>().eq(JxUserTransactionsDo::getUserId, userId));

        JSONObject resultData = new JSONObject();
        JSONArray formatedWithdrawRecords = new JSONArray();
        for (int k = 0 ; k < pageList.getRecords().size(); k ++) {
            JxUserTransactionsDo promotionDetailDo = pageList.getRecords().get(k);

            JSONObject formatedWithdrawRecord = new JSONObject();
            formatedWithdrawRecord.put("value", promotionDetailDo.getValue());
            formatedWithdrawRecord.put("time", promotionDetailDo.getCreateTime());
            formatedWithdrawRecord.put("orderId", promotionDetailDo.getId());

            formatedWithdrawRecords.add(formatedWithdrawRecord);
        }

        resultData.put("records", formatedWithdrawRecords);
        resultData.put("totalCount", pageList.getTotal());
        resultData.put("pages", pageList.getPages());

        return Result.ok(resultData);
    }

    /**
     * 接口 3.9 获取用户自己的分享二维码，其他用户可以通过扫描此二维码，打开小程序
     * @return
     */
    @GetMapping("/queryShareQRCode")
    public Result<?> queryShareQRCode(
        HttpServletRequest request,
        @RequestParam(value = "userId", required = false) String userIdForDebug) {

        String token = request.getHeader("X-Access-Token");
        String userId = JxUserUtil.getUserId(token, userIdForDebug);

//        String filePath = "D:\\workspace\\" + userId + ".jpeg";
        String fileRootDir1 = "/usr/local/nginx/html/";
        // fileRootDir2 + qrCodeImageFileName 是url的相对路径部分，会存放在 db中
        String fileRootDir2 = "static/images/user/promotion_qrcode/";
        String qrCodeImageFileName = userId + ".jpeg";
        String filePath = fileRootDir1 + fileRootDir2 + qrCodeImageFileName;
        File userQRCodeImage = new File(filePath);
        if (!userQRCodeImage.exists()) {
            // 1.调用微信 https://api.weixin.qq.com/cgi-bin/token 接口，获取代表我们自己的小程序 app secret的 access_token
            // 参考postman中的 3.9.1
            // 微信开发者文档：https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code/getUnlimitedQRCode.html
            String getAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + jxCryptUtil.appId + "&secret=" + jxCryptUtil.appSecret ;
            String responseBody = JxHttpTemplate.httpGet(getAccessTokenUrl);
            JSONObject accessTokenInfo = JSONObject.parseObject(responseBody);
            String accessToken = accessTokenInfo.getString("access_token");

            // 2. 调用 https://api.weixin.qq.com/wxa/getwxacodeunlimit 接口，获取可以生成无限个二维码图片的接口，获取基于本用户id，编码后的小程序码 二维码图片
            String getQRCodeUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken;
            // 当用户的推广二维码不存在时，才调用接口去重新生成
            // JSONObject requestBody = new JSONObject(); requestBody.toJSONString() 得到的字符串，会导致接口报错 {"errcode":47001,"errmsg":"data format error rid: 659fc1c4-43012ce5-3c258a10"}
//            String requestBody = "{\r\n \"page\": \"pages/index/index\",\r\n \"scene\": \"userId="+ userId +"\",\r\n \"check_path\": false,\r\n \"env_version\": \"trial\"\r\n}";
            // 返回最小 width x height 的二维码，减小二维码图片下载的时间消耗，最小 280 x 280 最大 1280 x 1280，默认 430 x 430
            String requestBody = "{\n" +
                    "    \"page\": \"pages/index/index\",\n" +
                    "    \"scene\": \"userId=xxxxx\",\n" +
                    "    \"check_path\": false,\n" +
                    "    \"env_version\": \"trial\",\n" +
                    "    \"width\":280\n" +
                    "}";
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "application/json");
            boolean result = JxHttpTemplate.httpDownloadFile(getQRCodeUrl, HttpMethod.POST, requestBody, headers, filePath);
            if (result == false) {
                return Result.error("获取用户的分享码失败，请联系后台技术人员进行处理");
            }
        }
        JSONObject resultData = new JSONObject();
        String hostName = "http://btprint.yongshantuan.cn/";
        resultData.put("qrcodeUrl", hostName + fileRootDir2 + qrCodeImageFileName);
        resultData.put("userId", JxCustomerConstant.USER_ID_PREFIX + userId.substring(userId.length() - 8)); // 在小程序-我的页面，左上角，仅展示用户id的最后8位；
        resultData.put("backgroupdImageUrl", JxCustomerConstant.PROMOTION_CARD_BACKGOURD_IMAGE_URL); // 推广名片背景图

        return Result.ok(resultData);
    }

    /**
     * 接口 3.10
     * @param request
     * @param sharedUserId 分享者的id，老用户的id； 新用户，被分享后的用户的id在 token中
     * @return
     */
    @PutMapping("/sendSharedUserId")
    public Result<?> sendSharedUserId(
            HttpServletRequest request,
            @RequestParam(value = "userId", required = false) String userIdForDebug,
            @RequestParam("sharedUserId")  String sharedUserId) {

        String token = request.getHeader("X-Access-Token");
        String userId = JxUserUtil.getUserId(token, userIdForDebug);
        String errMsg = saveShareEventAndAssignBonus(sharedUserId, userId);

        if (errMsg.isEmpty()) {
            return Result.ok("推广关系关联成功");
        } else {
            return Result.error(errMsg);
        }
    }

    /**
     * 记录 分享事件，并向老用户派发 拉新奖励，会向两张表中添加记录
     * 1. 向 jx_promotion_detail 表中添加一条记录 老用户分享新用户的记录；当通过扫码打开小程序时，会调用 sendSharedUserId 接口，此接口只会向 jx_promotion_detail表添加一次记录，不能添加多条
     * 2. 当新用户通过扫码打开小程序时，需要根据系统配置，向老用户发放一次拉新奖励，具体数值有 jx_sys_config表中的 promotion_introduce_new_user_bonus 定义的金额
     */
    @Transactional(rollbackFor = Exception.class)
    public String saveShareEventAndAssignBonus(String shareOldUserId, String promotedNewUserId) {

        String errMsg = "";
        Long count = jxUserMapper.selectCount(new LambdaQueryWrapper<JxUserDo>().eq(JxUserDo::getId, promotedNewUserId));

        if (count == 0) {
            try {
                // 1. 向 推广记录表中，记录下 老用户向新用户 发送分享二维码，新用户扫码 打开小程序 这一操作的记录
                JxPromotionDetailDo promotionDetailDo = new JxPromotionDetailDo();
                promotionDetailDo.setShareUserId(shareOldUserId);
                promotionDetailDo.setPromotedUserId(promotedNewUserId);
                promotionDetailDo.setCreateTime(new Date());
                jxPromotionDetailMapper.insert(promotionDetailDo);

                // 2. 向 jx_user_transactions 交易记录表中添加一条 拉新用户奖励，由系统 为 用户的钱包中充值 promotion_introduce_new_user_bonus 定义的金额
                JxUserTransactionsDo userTransactionsDo = new JxUserTransactionsDo();
                userTransactionsDo.setUserId(shareOldUserId);
                userTransactionsDo.setType(101);
                String introduceNewUserBonusConfigValue = (String)redisUtil.hget(JxRedisConstant.JX_SYS_CONFIG_KEY, JxSysConfigConstants.PROMOTION_INTRODUCE_NEW_USER_BONUS);
                Integer introduceNewUserBonus = Integer.parseInt(introduceNewUserBonusConfigValue);
                userTransactionsDo.setValue(introduceNewUserBonus);
                userTransactionsDo.setOrderId(""); // 不是新用户下单，老用户获取的分成，而是 拉新后的一次性奖励，因此没有订单号
                jxUserTransactionsMapper.insert(userTransactionsDo);

                // 3. 将用户信息中，promotion_balance 增加 拉新用户奖励值
                JxUserDo jxUserDo = jxUserMapper.selectById(shareOldUserId);
                Integer updatedRemaingPromotionBonus = jxUserDo.getRemainingPromotionValue() + introduceNewUserBonus;

                JxUserDo updatedUserDo = new JxUserDo();
                updatedUserDo.setId(shareOldUserId);
                updatedUserDo.setRemainingPromotionValue(updatedRemaingPromotionBonus);
                jxUserMapper.updateById(updatedUserDo);

            } catch (Exception ex) {
                log.error("saveShareEventAndAssignBonus: {}", ex);
                throw ex;
            }
        } else {

            log.warn("您 {} 所分享的新用户：{} 已经存在，可能已通过其他用户分享码打开小程序，不能重复分享", shareOldUserId, promotedNewUserId);
            errMsg = "分享的用户已存在，可能已通过其他用户分享码打开小程序，不能重复分享";
        }
        return errMsg;
    }


    /**
     * 接口 3.11  我的推广-推广明细页面，展示推广佣金详情
     * @param request
     * @param userIdForDebug
     * @return
     */
    @GetMapping("/queryPromotionCommissionDetails")
    public Result<?> queryPromotionCommissionDetails(
            HttpServletRequest request,
            @RequestParam("pageNo") Integer pageNo,
            @RequestParam("pageSize") Integer pageSize,
            @RequestParam(value = "userIdForDebug", required = false) String userIdForDebug) {

        String token = request.getHeader("X-Access-Token");
        String userId = JxUserUtil.getUserId(token, userIdForDebug);

        List<Integer> promotionComissionTypeCode = new ArrayList<>();
        promotionComissionTypeCode.add(101);
        promotionComissionTypeCode.add(102);
        Page<JxUserTransactionsDo> page = new Page<>(pageNo, pageSize);

        Page<JxUserTransactionsDo> pageList = jxUserTransactionsMapper.selectPage(page,
                new LambdaQueryWrapper<JxUserTransactionsDo>()
                    .eq(JxUserTransactionsDo::getUserId, userId)
                    .in(JxUserTransactionsDo::getType, promotionComissionTypeCode));
        JSONArray formatedTransactons = new JSONArray();
        for (int k = 0 ; k < pageList.getRecords().size(); k ++) {
            JxUserTransactionsDo transactionsDo = pageList.getRecords().get(k);
            JSONObject formatedTransactionData = new JSONObject();
            formatedTransactionData.put("title", transactionsDo.getDescription());
            formatedTransactionData.put("value", transactionsDo.getValue());
            formatedTransactionData.put("orderId", transactionsDo.getOrderId());
            formatedTransactionData.put("time", transactionsDo.getCreateTime());

            formatedTransactons.add(formatedTransactionData);
        }

        JSONObject resultData = new JSONObject();
        resultData.put("totalCount", pageList.getTotal());
        resultData.put("pages", pageList.getPages());
        resultData.put("transactions", formatedTransactons);

        return Result.ok(resultData);
    }

}
